Skip to content

chat: enforce required tagged tool args in any order via permute() - #26472

Open
i386 wants to merge 1 commit into
ggml-org:masterfrom
Mesh-LLM:agent/llama-tagged-tool-argument-order
Open

chat: enforce required tagged tool args in any order via permute()#26472
i386 wants to merge 1 commit into
ggml-org:masterfrom
Mesh-LLM:agent/llama-tagged-tool-argument-order

Conversation

@i386

@i386 i386 commented Aug 2, 2026

Copy link
Copy Markdown

Overview

Fixes the generic tagged tool-call output parser (arg_key/arg_value tags, e.g. the GLM-4.7-Flash template): it required all required arguments to be emitted in schema definition order, before any optional ones, and rejected valid calls where the model reordered the required arguments. JSON object member order is semantically irrelevant, so required arguments must be accepted in any order.

This does not touch the Qwen path — Qwen tool calls go through their own specialized parser (thanks @aldehir).

Approach

Required arguments are matched through permute() (common/chat-peg-parser.cpp), which generates every ordering while requiring each argument exactly once — the same construction the generic JSON tool path already uses (common/chat.cpp). The grammar therefore structurally rejects a call that omits or duplicates a required argument; there is no relaxed grammar plus post-hoc mapper-side validation and no bespoke runtime error. A malformed call simply does not match the grammar, which keeps the grammar sampler enforcing the schema during constrained decoding.

Optional arguments follow the required ones as a trailing run, in any order, each at most once — the ordering of required + optional is kept, matching the JSON tool path.

Caveat: above COMMON_CHAT_MAX_PERMUTE (6) required arguments, permute() falls back to a fixed sequence — the same policy the JSON tool path already applies.

Tests

  • reversed required order parses and round-trips (GLM-4.7-Flash tagged format), with an optional argument in the trailing run
  • omitted required argument: rejected by the grammar itself ("does not match the expected ... format")
  • duplicated required argument: rejected by the grammar itself
  • optional argument before a required one: rejected by the grammar (ordering of required + optional is kept)
  • full test-chat, test-chat-peg-parser and test-chat-auto-parser suites pass

Requirements

  • YES I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, AI was used to find and fix this problem. I have eyeballed the code and manually tested

@i386
i386 requested review from a team and pwilkin as code owners August 2, 2026 20:40
@github-actions github-actions Bot added the testing Everything test related label Aug 2, 2026
@i386

i386 commented Aug 25, 2026

Copy link
Copy Markdown
Author

@pwilkin could I please get your eyes on this one?

@aldehir

aldehir commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

If this is targeting the Qwen models, those have moved to a specialized parser.

Regardless, the changes here prevent the grammar sampler from enforcing required arguments. In its place, an error is returned if the model duplicates or omits a required property. That is not a solution, in my opinion. If anything, the request should succeed and allow the client to send a descriptive error message so the model can self-correct.

In my experiments, Qwen follows the order in properties a majority of the time. It helps if the properties in required are also listed in the same order. We have updated the Qwen grammar (and parser) to accept permutations of required arguments in case it deviates.

If the intent is to target other models, then a better solution is to leverage the recent p.permute() helper instead.

@i386
i386 force-pushed the agent/llama-tagged-tool-argument-order branch from cdf4755 to 46fec96 Compare August 29, 2026 21:10
@i386 i386 changed the title Fix issue where tool arguments can appear in any JSON-valid order chat: accept tagged tool-call arguments in any order via permute() Aug 29, 2026
i386 added a commit to Mesh-LLM/mesh-llm that referenced this pull request Aug 29, 2026
Rework the tagged tool-call argument grammar in patch 0010 per upstream
review of ggml-org/llama.cpp#26472: required arguments are now matched
through permute(), which accepts any ordering while requiring each
argument exactly once, so the grammar structurally rejects omitted and
duplicated required arguments instead of accepting them and throwing a
bespoke mapper-side error at parse time. Optional arguments keep a
zero_or_more choice and may interleave anywhere among required ones
(each permuted required slot is optional_run + required + space, plus a
trailing optional_run, so optional args emitted between two required
args still parse).

Delete the mapper-side TOOL_REQUIRED_ARG_PREFIX tagging, the
required_args/seen_args bookkeeping, and the Missing/Duplicate tool
argument throws those supported. Update the two throw-asserting tests
to assert grammar-level rejection instead. Mirrors the construction
the generic JSON tool path already uses (common/chat.cpp), including
the COMMON_CHAT_MAX_PERMUTE=6 fallback-to-sequence policy.

Re-verified on the prepared tree: prepare-llama.sh pinned re-applies
all patches cleanly; test-chat-auto-parser (533), test-chat-peg-parser
(210) green; focused driver cases (interleaved, reversed, missing,
duplicate-required, duplicate-optional) all correct.

Signed-off-by: James Dumay <jameswdumay@gmail.com>
Co-authored-by: Son Of Dario <f551f0290dbf6b68fe126a2679e2aec49f059b6d0d295923fe300fdd71c8ffb6@meshllm.communities.buzz.xyz>
Co-authored-by: jy <6b90287aa37add903ef8b4200477ecbfa85ac0da1a84cf1ff51a7207d0220740@meshllm.communities.buzz.xyz>
i386 added a commit to Mesh-LLM/mesh-llm that referenced this pull request Aug 29, 2026
Rework the tagged tool-call argument grammar in patch 0010 per upstream
review of ggml-org/llama.cpp#26472: required arguments are now matched
through permute(), which accepts any ordering while requiring each
argument exactly once, so the grammar structurally rejects omitted and
duplicated required arguments instead of accepting them and throwing a
bespoke mapper-side error at parse time. Optional arguments keep a
zero_or_more choice and may interleave anywhere among required ones
(each permuted required slot is optional_run + required + space, plus a
trailing optional_run, so optional args emitted between two required
args still parse).

Delete the mapper-side TOOL_REQUIRED_ARG_PREFIX tagging, the
required_args/seen_args bookkeeping, and the Missing/Duplicate tool
argument throws those supported. Update the two throw-asserting tests
to assert grammar-level rejection instead. Mirrors the construction
the generic JSON tool path already uses (common/chat.cpp), including
the COMMON_CHAT_MAX_PERMUTE=6 fallback-to-sequence policy.

Re-verified on the prepared tree: prepare-llama.sh pinned re-applies
all patches cleanly; test-chat-auto-parser (533), test-chat-peg-parser
(210) green; focused driver cases (interleaved, reversed, missing,
duplicate-required, duplicate-optional) all correct.

Signed-off-by: James Dumay <jameswdumay@gmail.com>
Co-authored-by: Son Of Dario <f551f0290dbf6b68fe126a2679e2aec49f059b6d0d295923fe300fdd71c8ffb6@meshllm.communities.buzz.xyz>
Co-authored-by: jy <6b90287aa37add903ef8b4200477ecbfa85ac0da1a84cf1ff51a7207d0220740@meshllm.communities.buzz.xyz>
@i386

i386 commented Aug 29, 2026

Copy link
Copy Markdown
Author

@aldehir that makes a lot of sense and thank you for your gentle feedback here :) I've updated using permute as requested.

@aldehir

aldehir commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Optional arguments may appear in any order, any number of times, and interleaved anywhere among the required ones (before each required argument and after the last).

I don't want to support this, because it further complicates the generated grammar. We have to be mindful of how many active stacks may exist at any point in time in the grammar sampler.

https://developers.openai.com/api/docs/guides/structured-outputs gives some insight in how OpenAI is handling this:

To use Structured Outputs, all fields or function parameters must be specified as required.

Although all fields must be required (and the model will return a value for each parameter), it is possible to emulate an optional parameter by using a union type with null.

Right now "type": ["string", "null"] is not supported because we collapse any union that includes a string into a string. I'm working on improving this for Qwen, which can then be applied generally.

For now, we should keep the ordering of required + optional.

i386 added a commit to Mesh-LLM/mesh-llm that referenced this pull request Aug 29, 2026
Port the ggml-org/llama.cpp#26472 reviewer-preferred design onto main's
patch queue (the tagged tool-arg rework previously staged on the
jd/apple-core-ai stack as 0010): required arguments in the tagged
tool-call grammar are matched through permute(), which accepts any
ordering while requiring each argument exactly once, so the grammar
structurally rejects omitted and duplicated required arguments instead
of accepting them and throwing a bespoke mapper-side error at parse
time. Optional arguments keep a zero_or_more choice and may interleave
anywhere among required ones (each permuted required slot is
optional_run + required + space, plus a trailing optional_run).

Deletes the TOOL_REQUIRED_ARG_PREFIX tagging, the required_args /
seen_args bookkeeping, and the Missing/Duplicate tool argument throws.
The two throw-asserting tests now assert grammar-level rejection
("does not match the expected format"). Mirrors the generic JSON tool
path construction (common/chat.cpp), including the
COMMON_CHAT_MAX_PERMUTE = 6 fallback-to-sequence policy.

Verified against main's queue: prepare-llama.sh pinned applies all 32
patches clean onto pin cc83d7b4; test-chat-auto-parser and
test-chat-peg-parser suites green on the prepared tree; the new
interleaved/reversed/omitted/duplicated tagged-arg cases pass under
test-chat --template GLM-4.7-Flash. Full test-chat is blocked by a
pre-existing, unrelated fixture crash at test_msgs_oaicompat_json_conversion
(reproduced identically on a pristine-main prepared tree with this
change absent).

Signed-off-by: James Dumay <jameswdumay@gmail.com>
Co-authored-by: Son Of Dario <f551f0290dbf6b68fe126a2679e2aec49f059b6d0d295923fe300fdd71c8ffb6@meshllm.communities.buzz.xyz>
Co-authored-by: jy <6b90287aa37add903ef8b4200477ecbfa85ac0da1a84cf1ff51a7207d0220740@meshllm.communities.buzz.xyz>
@i386
i386 force-pushed the agent/llama-tagged-tool-argument-order branch from 46fec96 to a53deca Compare August 30, 2026 00:35
Object member order is semantically irrelevant, so tool calls in the
generic tagged format (arg_key/arg_value tags) may emit required
arguments in any order.

Required arguments are now matched through permute(), which generates
every ordering while requiring each argument exactly once -- the same
approach the generic JSON tool path already uses (common/chat.cpp).
The grammar therefore structurally rejects a call that omits or
duplicates a required argument, instead of accepting it and throwing a
bespoke mapper-side error at parse time. This keeps malformed calls out
of the server and hands the client a descriptive "does not match the
expected format" error.

Optional arguments follow the required ones as a trailing run, in any
order, each at most once -- the ordering of required + optional is kept,
matching the JSON tool path. Above COMMON_CHAT_MAX_PERMUTE (6) required
args, permute() falls back to a fixed sequence -- the same policy as the
JSON tool path.

Tests cover reversed required order, an optional argument in the
trailing run, and grammar-level rejection of omitted and duplicated
required arguments and of optional-before-required ordering.
@i386
i386 force-pushed the agent/llama-tagged-tool-argument-order branch from a53deca to 264f66c Compare August 30, 2026 00:35
@i386 i386 changed the title chat: accept tagged tool-call arguments in any order via permute() chat: enforce required tagged tool args in any order via permute() Aug 30, 2026
@i386

i386 commented Aug 30, 2026

Copy link
Copy Markdown
Author

@aldehir fair call - have updated to match what you've asked for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

testing Everything test related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants